package org.vison.framework.doc;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.vison.framework.doc.annotation.ApiCategory;
import org.vison.framework.doc.annotation.ApiMeta;
import org.vison.framework.doc.annotation.ApiProperty;
import org.vison.framework.doc.annotation.DocLink;
import org.vison.framework.doc.config.DocProperties;
import org.vison.framework.doc.web.ApiInfo;
import org.vison.framework.doc.web.TypeFieldResolver;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * REST接口文档扫描器
 * 负责扫描指定包路径下的Spring MVC控制器，解析REST接口信息
 */
@Slf4j
@Component
public class DocScanner {

    @Autowired
    private DocProperties docProperties;

    /**
     * 扫描到的接口文档信息列表
     */
    private List<ApiInfo> apiInfoList = new ArrayList<>();

    /**
     * 初始化扫描器，在Spring容器启动时执行
     */
    @PostConstruct
    public void init() {
        // 如果接口文档功能未启用，则不执行扫描
        if (!docProperties.isEnabled()) {
            log.info("接口文档功能已禁用");
            return;
        }
        
        List<String> scanPackages = docProperties.getRest().getScanPackages();
        if (scanPackages == null || scanPackages.isEmpty()) {
            log.warn("REST接口文档扫描包路径未配置，无法扫描接口");
            return;
        }
        
        // 执行扫描逻辑
        scanRestControllers(scanPackages);
        
        log.info("REST接口文档扫描完成，共扫描到 {} 个接口", apiInfoList.size());
    }

    /**
     * 扫描指定包路径下的REST控制器
     * 
     * @param scanPackages 扫描的包路径列表
     */
    private void scanRestControllers(List<String> scanPackages) {
        log.info("扫描REST控制器，包路径: {}", scanPackages);
        
        try {
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            
            for (String basePackage : scanPackages) {
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX 
                        + basePackage.replace('.', '/') + "/**/*.class";
                
                Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
                
                for (Resource resource : resources) {
                    MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    
                    // 加载类并检查是否有Controller或RestController注解
                    Class<?> controllerClass = Class.forName(className);
                    
                    if (controllerClass.isAnnotationPresent(Controller.class) || 
                            controllerClass.isAnnotationPresent(RestController.class)) {
                        
                        // 获取类级别的@RequestMapping注解
                        RequestMapping classRequestMapping = AnnotationUtils.findAnnotation(controllerClass, RequestMapping.class);
                        String classPath = classRequestMapping != null && classRequestMapping.value().length > 0 
                                ? classRequestMapping.value()[0] : "";

                        // 获取类级别的@ApiCategory注解
                        ApiCategory apiCategory = AnnotationUtils.findAnnotation(controllerClass, ApiCategory.class);
                        String categoryName =  controllerClass.getSimpleName();
                        if (apiCategory != null && !apiCategory.categoryName().isEmpty()) {
                            categoryName = apiCategory.categoryName();
                        }

                        // 扫描所有方法
                        for (Method method : controllerClass.getDeclaredMethods()) {
                            // 检查方法是否有REST相关的映射注解
                            ApiInfo apiInfo = extractApiInfoFromMethod(controllerClass, method, classPath);
                            if (apiInfo != null) {
                                apiInfo.setApiCategory(categoryName);
                                apiInfoList.add(apiInfo);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("扫描REST控制器失败", e);
        }
    }
    
    /**
     * 从方法中提取API信息
     */
    private ApiInfo extractApiInfoFromMethod(Class<?> controllerClass, Method method, String classPath) {
        // 检查方法是否有HTTP方法注解
        Annotation[] annotations = method.getAnnotations();
        String httpMethod = null;
        String methodPath = "";
        
        for (Annotation annotation : annotations) {
            if (annotation instanceof GetMapping) {
                httpMethod = "GET";
                GetMapping getMapping = (GetMapping) annotation;
                methodPath = getMapping.value().length > 0 ? getMapping.value()[0] : "";
                break;
            } else if (annotation instanceof PostMapping) {
                httpMethod = "POST";
                PostMapping postMapping = (PostMapping) annotation;
                methodPath = postMapping.value().length > 0 ? postMapping.value()[0] : "";
                break;
            } else if (annotation instanceof PutMapping) {
                httpMethod = "PUT";
                PutMapping putMapping = (PutMapping) annotation;
                methodPath = putMapping.value().length > 0 ? putMapping.value()[0] : "";
                break;
            } else if (annotation instanceof DeleteMapping) {
                httpMethod = "DELETE";
                DeleteMapping deleteMapping = (DeleteMapping) annotation;
                methodPath = deleteMapping.value().length > 0 ? deleteMapping.value()[0] : "";
                break;
            } else if (annotation instanceof RequestMapping) {
                RequestMapping requestMapping = (RequestMapping) annotation;
                if (requestMapping.method().length > 0) {
                    httpMethod = requestMapping.method()[0].name();
                } else {
                    httpMethod = "GET";
                }
                methodPath = requestMapping.value().length > 0 ? requestMapping.value()[0] : "";
                break;
            }
        }


        // 如果没有找到映射注解，跳过
        if (httpMethod == null) {
            return null;
        }
        
        // 构建完整路径
        String fullPath = classPath + (methodPath.startsWith("/") ? methodPath : "/" + methodPath);
        // 规范化路径
        fullPath = fullPath.replace("//", "/");
        // 检查方法是否有ApiMeta注解
        ApiMeta apiMeta = method.getAnnotation(ApiMeta.class);
        String apiName = method.getName();
        String description = "";
        int order = 0;

        // 生成接口ID
        String id = DigestUtils.md5Hex(controllerClass.getName() + method.getName());
        // 创建API信息对象
        ApiInfo apiInfo = new ApiInfo();
        if (apiMeta != null && !apiMeta.name().isEmpty() && !apiMeta.description().isEmpty()) {
            apiName = apiMeta.name();
            description = apiMeta.description();
            order = apiMeta.order();
            List<DocLink> docLinks = Arrays.stream(apiMeta.docLinks())
                    .map(docLink -> new DocLink(docLink.url(), docLink.title()))
                    .collect(Collectors.toList());
            apiInfo.setDocLinks(docLinks);
        }
        apiInfo.setId(id);
        apiInfo.setApiType("rest");
        apiInfo.setPath(fullPath);
        apiInfo.setMethod(httpMethod);
        apiInfo.setName(apiName);
        apiInfo.setControllerClass(controllerClass.getName());
        apiInfo.setMethodName(method.getName());
        apiInfo.setDescription(description);
        apiInfo.setOrder(order);

        // 提取方法参数信息
        extractMethodParams(method, apiInfo);
        
        return apiInfo;
    }

    
    /**
     * 提取方法参数信息
     */
    private void extractMethodParams(Method method, ApiInfo apiInfo) {
        // 处理请求参数
        ApiInfo.ApiModel requestBodyModel = buildRequestModel(method);
        apiInfo.setRequestBodyModel(requestBodyModel);
        
        // 处理响应模型
        ApiInfo.ApiModel responseModel = buildResponseModel(method);
        apiInfo.setResponseModel(responseModel);
    }
    
    /**
     * 构建请求参数模型
     */
    private ApiInfo.ApiModel buildRequestModel(Method method) {
        Parameter[] parameters = method.getParameters();
        // 初始化请求体模型
        ApiInfo.ApiModel requestBodyModel = new ApiInfo.ApiModel();
        requestBodyModel.setType("mixed");
        
        for (Parameter parameter : parameters) {
            ApiInfo.ApiParam apiParam = new ApiInfo.ApiParam();
            apiParam.setName(parameter.getName());
            
            // 获取参数的泛型类型信息
            Type paramType = parameter.getParameterizedType();
            FieldTypeUtils.TypeInfo typeInfo = FieldTypeUtils.getTypeInfo(paramType);
            apiParam.setFullType(typeInfo.getQualifiedName());
            apiParam.setSimpleType(typeInfo.getSimpleName());

            // 检查参数注解
            if (parameter.isAnnotationPresent(RequestParam.class)) {
                RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
                apiParam.setRequired(requestParam.required());
                if (!"" .equals(requestParam.defaultValue())) {
                    apiParam.setDefaultValue(requestParam.defaultValue());
                }
                if (!"" .equals(requestParam.value())) {
                    apiParam.setName(requestParam.value());
                }
                apiParam.setDescription("Query parameter");
                requestBodyModel.addParam(apiParam);
            } else if (parameter.isAnnotationPresent(PathVariable.class)) {
                PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
                apiParam.setRequired(true);
                if (!pathVariable.value().isEmpty()) {
                    apiParam.setName(pathVariable.value());
                }
                apiParam.setDescription("Path variable");
                requestBodyModel.addParam(apiParam);
            } else if (parameter.isAnnotationPresent(RequestBody.class)) {
                // 记录请求体类型，使用完整的泛型类型信息  仅有一个参数能使用
                requestBodyModel = buildRequestBodyModel(parameter);
            } else {
                // todo: 处理其他参数类型
            }
        }

        return requestBodyModel;
    }

    private ApiInfo.ApiModel buildRequestBodyModel(Parameter parameter) {
        ApiInfo.ApiModel requestBodyModel = TypeFieldResolver.resolveParameterTypeFields(parameter);
        return requestBodyModel;
    }


    /**
     * 构建响应模型
     */
    private ApiInfo.ApiModel buildResponseModel(Method method) {
        // 解析返回类型的字段信息，确保嵌套结构被正确处理
        ApiInfo.ApiModel responseModel = TypeFieldResolver.resolveMethodReturnTypeFields(method);
        return responseModel;
    }


    /**
     * 获取扫描到的所有REST接口信息
     * 
     * @return REST接口信息列表
     */
    public List<ApiInfo> getApiInfoList() {
        return apiInfoList;
    }
    
    /**
     * 根据ID获取REST接口信息
     * 
     * @param id 接口ID
     * @return 接口信息，如果不存在则返回null
     */
    public ApiInfo getApiInfoById(String id) {
        for (ApiInfo apiInfo : apiInfoList) {
            if (id.equals(apiInfo.getId())) {
                return apiInfo;
            }
        }
        return null;
    }
}